package com.lime49.lockcrypt;

import com.lime49.lockcrypt.ui.PasswordDialog;
import java.io.*;
import java.util.*;
import java.net.*;
import javax.swing.JOptionPane;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import org.w3c.dom.*;
import java.text.MessageFormat;
import com.thoughtworks.xstream.*;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import java.security.SecureRandom;
import java.security.Security;
import java.text.SimpleDateFormat;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang.StringEscapeUtils;

public class External {
    private ResourceBundle lang;
    private boolean includeAccounts,
                    allAccounts,
                    includeTypes;
    private HashMap<Account, Boolean> accountsToExport;
    private ArrayList<AccType> types;
    
    /**
     * Creates a new instance of External which uses the specified ResourceBundle for it's language strings
     * @param lang The ResourceBundle to use for language strings
     * @param accounts The accounts to export
     * @param allAccounts <c>true</c> if all accounts should be exported, <c>false</c>. if only selected accounts should be exported
     * @param includeAccounts <c>true</c> to include accounts, otherwise <c>false</c>
     * @param types The account types to export
     * @param includeTypes <c>true</c> to include account types, otherwise <c>false</c>
     */
    public External(ResourceBundle lang, HashMap<Account, Boolean> accounts, boolean allAccounts, boolean includeAccounts, ArrayList<AccType> types, boolean includeTypes) {
        this.lang = lang;
        this.includeAccounts = includeAccounts;
        this.includeTypes = includeTypes;
        this.accountsToExport = accounts;
        this.types = types;
    }
    
    /**
     * Exports the specified list of accounts to CSV format
     * @param file The file to write to
     */
    public void exportToCSV(File file) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            if(includeAccounts) {
                for(Map.Entry<Account, Boolean> entry : accountsToExport.entrySet()) {
                    if(!allAccounts && !entry.getValue()) {
                        continue;
                    }
                    Account acc = entry.getKey();
                    if(!acc.getIsDeleted()) {
                        LinkedHashMap<String, String> fieldMap = acc.getTypeObject().getFieldMap();
                        ArrayList<String> fields = new ArrayList<String>();
                        for(String fieldName : fieldMap.keySet()) {
                            if(!fieldMap.get(fieldName).isEmpty()) {
                                fields.add(fieldName+": "+fieldMap.get(fieldName));
                            }
                        }
                        String[] fieldArray = fields.toArray(new String[fields.size()]);
                        bos.write(new String("\""+acc.getName()+"\",\""+acc.getType()+"\",\""+acc.getTypeID()+"\",\""+acc.getGroup()+"\",\""+acc.getIcon()+"\",\""+acc.getCreated()+"\",\""+acc.getModified()+"\",\""+acc.getNotes()+"\",\""+Utils.implode(fieldArray, ",")+"\"\n").getBytes("utf-8"));
                    }
                }
            } else if(includeTypes) {
                for(AccType type : types) {
                    if(!type.isDeleted) {
                        bos.write(new String("\""+type.getName()+"\",\""+type.getID()+"\",\""+type.getShowNotes()+"\",\""+Utils.implode(type.fields, ",")+"\"\n").getBytes("utf-8"));
                    }
                }
            }
            bos.close();
        } catch(IOException e) {
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#205: "+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Exports the specified account list to plain text
     * @param file The file to write to
     */
    public void exportToTxt(File file) {
        try {
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
            if(includeAccounts) {
                for(Map.Entry<Account, Boolean> entry : accountsToExport.entrySet()) {
                    if(!allAccounts && !entry.getValue()) {
                        continue;
                    }
                    Account acc = entry.getKey();
                    if(!acc.getIsDeleted()) {
                        bos.write(new String("["+acc.getName()+"]\n").getBytes("utf-8"));
                        bos.write(new String("Group: "+acc.getGroup()+"\n").getBytes("utf-8"));
                        bos.write(new String("Type: "+acc.getType()+"\n").getBytes("utf-8"));
                        bos.write(new String("Icon: "+acc.getIcon()+"\n").getBytes("utf-8"));
                        bos.write(new String("Created: "+acc.getDateCreatedPretty(format)+"\n").getBytes("utf-8"));
                        bos.write(new String("Modified: "+acc.getDateModifiedPretty(format)+"\n").getBytes("utf-8"));
                        if(acc.getShowNotes() && !acc.getNotes().isEmpty()) {
                            bos.write(new String("Notes: "+acc.getNotes()+"\n").getBytes("utf-8"));
                        }
                        LinkedHashMap<String, String> fieldMap = acc.getTypeObject().getFieldMap();
                        for(String fieldName : fieldMap.keySet()) {
                            String fieldVal = fieldMap.get(fieldName);
                            if(!fieldVal.isEmpty()) {
                                bos.write(new String(fieldName.startsWith("~}") ? fieldName.substring(3) : fieldName+": "+fieldVal+"\n").getBytes("utf-8"));
                            }
                        }
                        bos.write(new String("\n").getBytes());
                    }
                }
            }
            if(includeTypes) {
                for(AccType type : types) {
                    if(!type.isDeleted) {
                        bos.write(new String("["+type.getName()+"]\n").getBytes("utf-8"));
                        bos.write(new String("ID: "+type.getID()+"\n").getBytes("utf-8"));
                        bos.write(new String("ShowNotes: "+type.getShowNotes()+"\n").getBytes("utf-8"));
                        bos.write(new String("Fields: "+Utils.implode(type.fields, ",")+"\n").getBytes("utf-8"));
                    }
                }
            }
            bos.close();
        } catch(IOException e) {
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#206: "+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Exports the specified account list to XML
     * @param file The file to write to
     * @param encrypt <c>true</c> if the file should be encrypted
     */
    public void exportToXML(File file, boolean encrypt) {
        XStream xstream = new XStream();
            xstream.registerConverter(new AccountConverter());
            xstream.alias("pwentry", Account.class);
            xstream.registerConverter(new TypeConverter());
            xstream.alias("type", AccType.class);
        try {
            ObjectOutputStream out = null;
            if(encrypt) {
                String key = PasswordDialog.ShowPasswordDialog(null, lang.getString("ENTERFILEPASS"), lang.getString("ENTERPASS"));
                if(key == null) { // user cancelled
                    return;
                }
                Security.addProvider(new com.sun.crypto.provider.SunJCE());
                SecretKeySpec secretKey = Utils.getKeySpec(key);
                byte[] iv = new byte[16];
                SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
                sr.nextBytes(iv);
                IvParameterSpec ivSpec = new IvParameterSpec(iv);

                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);

                FileOutputStream fos = new FileOutputStream(file);
                BufferedOutputStream bos = new BufferedOutputStream(fos);
                bos.write(iv);
                bos.flush();

                CipherOutputStream cos = new CipherOutputStream(bos, cipher);
                out = xstream.createObjectOutputStream(cos, "pwlist");
            } else {
                BufferedWriter br = new BufferedWriter(new FileWriter(file));
                br.write("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n");
                br.write("<!-- generator=\"LockCrypt "+Constants.LC_VER+" by Harry Jennerway\" -->\n");
                out = xstream.createObjectOutputStream(br, "pwlist");
            }
            if(includeAccounts) {
                for(Map.Entry<Account, Boolean> entry : accountsToExport.entrySet()) {
                    if(!allAccounts && !entry.getValue()) {
                        continue;
                    }
                    Account acc = entry.getKey();
                    if(!acc.getIsDeleted()) {
                        out.writeObject(acc);
                    }
                }
            }
            if(includeTypes) {
                for(AccType type : types) {
                    if(!type.isDeleted) {
                        out.writeObject(type);
                    }
                }
            }
            out.close();
        } catch(IOException e) {
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#207: "+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        } catch(Exception ex) {
            //todo: 1 - show error
        }
        //TODO: add accessory to save dialog to allow all fields as seperate elements or combined into 'Notes' field
    }
    
    /**
     * Exports the specified account list to HTML
     * @param file The file to write to
     */
    public void exportToHTML(File file) {
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(file.getPath()));
            StringBuffer buf = new StringBuffer();
            buf.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\n")
               .append("<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n<meta name=\"generator\" content=\"LockCrypt ")
               .append(Constants.LC_VER)
               .append("\" />\n<!-- Generated by LockCrypt by Harry Jennerway - http://www.lockcrypt.com -->\n");
            boolean cssIncluded = false;
            try {
                URL jarURL = getClass().getProtectionDomain().getCodeSource().getLocation();
                String jarDir = new File(jarURL.toURI()).getParent();
                File templateFile = new File(jarDir + File.separator + "lockcrypt.css");
                if(templateFile.exists()) {
                    try {
                        File filesDir = new File(file.getParent() + File.separator + "lockcrypt_files");
                        if(!filesDir.exists()) {
                            filesDir.mkdir();
                        }
                        copyFile(templateFile, new File(file.getParent() + File.separator + "lockcrypt_files" + File.separator + "lockcrypt.css"));
                        buf.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"lockcrypt_files/lockcrypt.css\" />\n");
                        cssIncluded = true;
                    } catch(Exception ex2) {
                        JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#208: "+ex2.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                    }
                }
            } catch(Exception ex) {
                JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#209: "+ex.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
                return;
            }

            if(!cssIncluded) {
                buf.append("<style type=\"text/css\">*{margin:0;padding:0;outline:0}html,body{ height:100%; min-height:100%}body{ margin:0; padding:0; font-size:62.5%; font-family:Verdana,Arial,Sans-Serif,Helvetica; background:#7F4D37; color:#000;}#wrapper{ margin:0 auto; background:#F9E8A3; width:50%; padding:1em}table.group{width:100%;font-size:1em;margin:0 auto 2em auto;border:1px solid #666;background:#faf4dc;padding:1em}div.footer{text-align:center}td.divider{width:100%;height:1em;text-align:center;font-size:1.5em;color:}hr{display:none}h1{margin:0.5em 0 1em 0}h2{font-size:1.8em}h3{font-size:1.4em;margin-left:6em}h4{font-size:1em;font-weight:bold;text-align:right;padding-right:0.5em}td.fieldname{width:25%}</style>\n");
            }
            buf.append("<title>")
               .append(lang.getString("HTML_TITLE"))
               .append("</title>\n</head>\n\n<body>\n<div id=\"wrapper\">\n\t<h1>")
               .append(lang.getString("HTML_TITLE"))
               .append("</h1>\n");
            HashMap<String, StringBuffer> accGroups = new HashMap<String, StringBuffer>();
            if(includeAccounts) {
                for(Map.Entry<Account, Boolean> entry : accountsToExport.entrySet()) {
                    if(!allAccounts && !entry.getValue()) {
                        continue;
                    }
                    Account acc = entry.getKey();
                    if(!acc.getIsDeleted()) {
                        String accGroup = acc.getGroup();
                        String accIcon = acc.getIcon();
                        StringBuffer groupBuffer;
                        if(!accGroups.containsKey(accGroup)) {
                            groupBuffer = new StringBuffer("\t<table class=\"group\">\n\t\t<tr>\n\t\t\t<td colspan=\"2\" class=\"grouptitle\"><h2>");
                            groupBuffer.append(accGroup)
                                       .append("</h2></td>\n\t\t</tr>\n");
                            accGroups.put(accGroup, groupBuffer);
                        } else {
                            groupBuffer = accGroups.get(accGroup);
                            groupBuffer.append("\t\t<tr><td colspan=\"2\" class=\"divider\">\n\t\t\t<p>&diams;&nbsp;&diams;&nbsp;&diams;</p><hr />\n\t\t</td></tr>\n");
                        }
                        groupBuffer.append("\t\t<tr>\n\t\t\t<td colspan=\"2\"><h3>")
                                   .append(StringEscapeUtils.escapeHtml(acc.getName()))
                                   .append("</h3></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"fieldname\"><h4>")
                                   .append(StringEscapeUtils.escapeHtml(lang.getString("TYPE")))
                                   .append("</h4></td>\n\t\t\t<td>")
                                   .append(StringEscapeUtils.escapeHtml(acc.getType()))
                                   .append("</td>\n\t\t</tr>\n");
                        LinkedHashMap<String, String> fieldMap = acc.getTypeObject().getFieldMap();
                        for(String fieldName : fieldMap.keySet()) {
                            String fieldVal = fieldMap.get(fieldName);
                            if(!fieldVal.isEmpty()) {
                                groupBuffer.append("\t\t<tr>\n\t\t\t<td class=\"fieldname\"><h4>")
                                           .append(StringEscapeUtils.escapeHtml(fieldName.startsWith("~}") ? fieldName.substring(3) : fieldName))
                                           .append("</h4></td>\n\t\t\t<td>")
                                           .append(StringEscapeUtils.escapeHtml(fieldVal))
                                           .append("<td>\n\t\t</tr>\n");
                            }
                        }
                        if(acc.getShowNotes() && !acc.getNotes().isEmpty()) {
                            groupBuffer.append("\t\t<tr>\n\t\t\t<td class=\"fieldname\"<h4>")
                                       .append(StringEscapeUtils.escapeHtml(lang.getString("NOTES")))
                                       .append("</h4></td>\n\t\t\t<td>")
                                       .append(StringEscapeUtils.escapeHtml(acc.getNotes()))
                                       .append("</td>\n\t\t</tr>\n");
                        }
                    }
                }
                for(StringBuffer group : accGroups.values()) {
                    group.append("\t</table>\n");
                    buf.append(group);
                }
            }
            if(includeTypes) {
                buf.append("\t<table class=\"group\">\n\t\t<tr>\n\t\t\t<td colspan=\"2\" class=\"grouptitle\"><h2>"+lang.getString("ACCOUNTTYPES")+"</h2></td>\n\t\t</tr>");
                boolean firstType = true;
                for(AccType type : types) {
                    if(!type.isDeleted) {
                        if(!firstType) {
                            buf.append("\t\t<tr><td colspan=\"2\" class=\"divider\">\n\t\t\t<p>&diams;&nbsp;&diams;&nbsp;&diams;</p><hr />\n\t\t</td></tr>\n");
                        }
                        firstType = false;
                        buf.append("\t\t<tr>\n\t\t\t<td colspan=\"2\"><h3>")
                                   .append(StringEscapeUtils.escapeHtml(type.getName()))
                                   .append("</h3></td>\n\t\t</tr>\n\t\t<tr>\n\t\t\t<td class=\"fieldname\"><h4>")
                                   .append(StringEscapeUtils.escapeHtml(lang.getString("FIELDS")))
                                   .append("</h4></td>\n\t\t\t<td>")
                                   .append(StringEscapeUtils.escapeHtml(Utils.implode(type.fields, ", ")))
                                   .append("</td>\n\t\t</tr>\n");
                    }
                }
                buf.append("\t</table>\n");
            }
            buf.append("\t<!-- Every time you remove a copyright notice, God kills a kitten. Please, think of the kittens. -->\n\t<div class=\"footer\">\n\t\t<p>Generated by <a href=\"http://www.lockcrypt.com/\">LockCrypt ")
               .append(Constants.LC_VER)
               .append("</a></p>\n\t\t<p>&copy; 2008 Harry Jennerway</p>\n\t</div>\n</div>\n</body>\n</html>");
            out.write(buf.toString());
            out.close();
        } catch(Exception e) {
            JOptionPane.showMessageDialog(null, lang.getString("ERROR")+"#210: "+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * Copies a file to a new location
     * @param in The file to copy
     * @param out The new file
     * @throws java.lang.Exception
     */
    public void copyFile(File in, File out) throws FileNotFoundException, IOException {
        FileInputStream fis  = new FileInputStream(in);
        FileOutputStream fos = new FileOutputStream(out);
        byte[] buf = new byte[1024];
        int i = 0;
        while((i=fis.read(buf))!=-1) {
            fos.write(buf, 0, i);
        }
        fis.close();
        fos.close();
    }
    
    /**
     * Imports accounts and types from the specified file, then refreshes the account tree
     * @param file The file to import
     * @param parent The FrmMainWindow instance which initiated the import
     * @param encrypt <c>true</c> if the file should be decrypted
     */
    public void importFromXML(File file, FrmMainWindow parent, boolean encrypt) {
        try {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss");
            long curDate = new Date().getTime();
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = null;
            if(encrypt) {
                String key = PasswordDialog.ShowPasswordDialog(parent, lang.getString("ENTERPASS_DESC"), lang.getString("ENTERPASS"));
                if(key == null) { // user cancelled
                    return;
                }
                Security.addProvider(new com.sun.crypto.provider.SunJCE());
                SecretKeySpec secretKey = Utils.getKeySpec(key);

                FileInputStream fis = new FileInputStream(file);
                BufferedInputStream bis = new BufferedInputStream(fis);

                byte[] iv = new byte[16];
                bis.read(iv, 0, 16);
                IvParameterSpec ivSpec = new IvParameterSpec(iv);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
                CipherInputStream cis = new CipherInputStream(bis, cipher);
                doc = db.parse(cis);
            } else {
                doc = db.parse(file);
            }
            doc.getDocumentElement().normalize();
            String name = null;
            if(includeAccounts) {
                NodeList accEntries = doc.getElementsByTagName("pwentry");
                String type = null, typeid = null, icon = null, group = null, notes = "";
                Long created = null, modified = null;
                AccType accType;
                boolean yesToAll = false;
                int noToAll = -1;
                for(int i=0;i<accEntries.getLength();i++) {
                    Node node = accEntries.item(i);
                    if(node.getNodeType() == Node.ELEMENT_NODE) {
                        Element thisNode = (Element)node;
                        NodeList subNodes = thisNode.getChildNodes();
                        name = null; type = null; typeid = null; icon = null; group = null; notes = "";
                        created = null; modified = null;
                        boolean showNotes = false;
                        LinkedHashMap<String, String> fields = new LinkedHashMap<String, String>();
                        NodeList fieldNodes = thisNode.getElementsByTagName("field"); // Added in 1.84
                        int numFields = fieldNodes.getLength(); // Added in 1.84
                        boolean pre184 = true;
                        if(numFields > 0) {
                            pre184 = false;
                        }
                        for(int j=0;j<subNodes.getLength();j++) {
                            Node thisAttributeNode = subNodes.item(j);
                            if(thisAttributeNode.getNodeType() == Node.ELEMENT_NODE) {
                                String thisAttribute = thisAttributeNode.getNodeName();
                                NodeList nodeVals = thisAttributeNode.getChildNodes();
                                if(nodeVals.getLength()>0) {
                                    String thisValue = nodeVals.item(0).getNodeValue();
                                    if(thisAttribute.equals("title")) {
                                        name = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else if(thisAttribute.equals("type")) {
                                        type = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else if(thisAttribute.equals("typeid")) {
                                        typeid = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else if(thisAttribute.equals("icon")) {
                                        if(Utils.in_array(Constants.iconNames, thisValue)) {
                                            icon = thisValue;
                                        } else {
                                            icon = "default";
                                        }
                                    } else if(thisAttribute.equals("group")) {
                                        group = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else if(thisAttribute.equals("created") || thisAttribute.equals("creationtime")) {
                                        created = df.parse(thisValue).getTime();
                                    } else if(thisAttribute.equals("modified") || thisAttribute.equals("lastmodtime")) {
                                        modified = df.parse(thisValue).getTime();
                                    } else if(thisAttribute.equals("shownotes")) {
                                        showNotes = Boolean.parseBoolean(thisValue);
                                    } else if(thisAttribute.equals("notes")) {
                                        notes = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else {
                                        if(thisAttribute.equals("fields")) {
                                            Node thisField;
                                            String fieldName;
                                            for(int k=0;k<numFields;k++) {
                                                thisField = fieldNodes.item(k);
                                                NamedNodeMap attributes = thisField.getAttributes();
                                                fieldName = attributes.getNamedItem("name").getNodeValue();
                                                fields.put(StringEscapeUtils.unescapeHtml(fieldName), StringEscapeUtils.unescapeHtml(thisField.getChildNodes().item(0).getNodeValue()));
                                            }
                                        } else if(pre184) {
                                            fields.put(StringEscapeUtils.unescapeHtml(thisAttribute.replaceAll("__", " ")), StringEscapeUtils.unescapeHtml(thisValue));
                                        }
                                    }
                                }
                            }
                        }
                        if(icon == null) {
                            icon = "default";
                        }
                        if(type == null) {
                            type = "Blank";
                        }
                        if(typeid == null) {
                            if(type.equalsIgnoreCase("website") || type.equalsIgnoreCase(lang.getString("WEBSITE"))) {
                                typeid = "__pass";
                            } else {
                                typeid = "GQFRST";
                            }
                        }
                        if(group == null) {
                            group = "LockCrypt";
                        } else if(!group.equals("LockCrypt") && Utils.getArrayListIndex(group, parent.accountGroups) < 0) { // if parent group doesn't exist, add it as a child of the root
                            parent.accountGroups.add(new Group(group, "LockCrypt", true));
                        }
                        if(name == null) {
                            name = Utils.getNextAvailableAccountName("Account",parent.accounts);
                        }
                        if(created == null) {
                            created = curDate;
                        }
                        if(modified == null) {
                            modified = curDate;
                        }
                        if(Utils.getTreeIndex(name, parent.accountTree) >= 0) {
                            if(noToAll > 0)
                                continue;
                            boolean overWrite = false;
                            if(yesToAll) {
                                overWrite = true;
                            } else {
                                MessageFormat formatter = new MessageFormat(lang.getString("OVERWRITEACCOUNT"));
                                Object[] buttonOptions = {lang.getString("YES"), lang.getString("YESTOALL"),lang.getString("NO"), lang.getString("NOTOALL")};
                                int ret = JOptionPane.showOptionDialog(parent, formatter.format(new Object[] {name}), lang.getString("INFORMATION"), JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, buttonOptions, buttonOptions[0]);
                                switch(ret) {
                                    case 0: // yes
                                        overWrite = true;
                                        break;
                                    case 1: // yes to all
                                        yesToAll = true;
                                        break;
                                    case 2: // no
                                        break;
                                    case 3:
                                        noToAll = 1;
                                        break;
                                }
                            }
                            if(overWrite) {
                                accType = new AccType(type, fields, showNotes,notes);
                                accType.setID(typeid);
                                parent.accounts.remove(Utils.getArrayListIndex(name, parent.accounts));
                                parent.accounts.add(new Account(name, type, icon, accType, true, group, created, modified));
                            }
                        } else {
                            accType = new AccType(type, fields, showNotes,notes);
                            accType.setID(typeid);
                            parent.accounts.add(new Account(name, type, icon, accType, true, group, created, modified));
                        }
                    }
                }
            }
            if(includeTypes) {
                NodeList typeEntries = doc.getElementsByTagName("type");
                for(int i=0;i<typeEntries.getLength();i++) {
                    Node node = typeEntries.item(i);
                    if(node.getNodeType() == Node.ELEMENT_NODE) {
                        Element thisNode = (Element)node;
                        NodeList subNodes = thisNode.getChildNodes();
                        name = null;
                        ArrayList<String> fieldsAry = new ArrayList<String>();
                        String[] fields;
                        boolean showNotes = false;
                        for(int j=0;j<subNodes.getLength();j++) {
                            Node thisAttributeNode = subNodes.item(j);
                            if(thisAttributeNode.getNodeType() == Node.ELEMENT_NODE) {
                                String thisAttribute = thisAttributeNode.getNodeName();
                                NodeList nodeVals = thisAttributeNode.getChildNodes();
                                if(nodeVals.getLength()>0) {
                                    String thisValue = nodeVals.item(0).getNodeValue();
                                    if(thisAttribute.equals("title")) {
                                        name = StringEscapeUtils.unescapeHtml(thisValue);
                                    } else if(thisAttribute.equals("field")) {
                                        fieldsAry.add(StringEscapeUtils.unescapeHtml(thisValue));
                                    } else if(thisAttribute.equals("shownotes")) {
                                        showNotes = Boolean.parseBoolean(thisValue);
                                    }
                                }
                            }
                        }
                        if(name != null && (Utils.getTypeIndex(name, parent.types, "types") < 0)) {
                            fields = new String[fieldsAry.size()];
                            fieldsAry.toArray(fields);
                            parent.types.add(new AccType(name, fields, true, false, showNotes));
                        }
                    }
                }
            }
            parent.refreshAccountTree();
        } catch(IOException e) {
            JOptionPane.showMessageDialog(parent, lang.getString("ERROR")+"#211: "+e.getLocalizedMessage(), lang.getString("ERR_CANTOPENFILE"), JOptionPane.ERROR_MESSAGE);
        } catch(Exception e) {
            JOptionPane.showMessageDialog(parent,lang.getString("ERROR")+"#212: "+e.getCause()+"\n"+e.getLocalizedMessage(), lang.getString("ERROR"), JOptionPane.ERROR_MESSAGE);
        }
        //TODO: add accessory to save dialog to allow all fields as seperate elements or combined into 'Notes' field
    }
    
    /**
    * Used by XStream to export accounts
    */
    class AccountConverter implements Converter {
        public boolean canConvert(Class classname) {
            return classname.equals(Account.class);
        }

        /**
         * Exports the specified account to XML
         * @param value The Account object
         * @param writer Used by XStream
         * @param context Used by XStream
         */
        public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
            Account account = (Account)value;
            writer.startNode("title");
                writer.setValue(StringEscapeUtils.escapeHtml(account.getName()));
                writer.endNode();
            writer.startNode("type");
                writer.setValue(StringEscapeUtils.escapeHtml(account.getType()));
                writer.endNode();
            writer.startNode("typeid");
                writer.setValue(StringEscapeUtils.escapeHtml(account.getTypeID()));
                writer.endNode();
            writer.startNode("icon");
                writer.setValue(account.getIcon());
                writer.endNode();
            writer.startNode("group");
                writer.setValue(StringEscapeUtils.escapeHtml(account.getGroup()));
                writer.endNode();
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss");
            writer.startNode("creationtime");
                writer.setValue(df.format(account.getDateCreated()));
                writer.endNode();
            writer.startNode("lastmodtime");
                writer.setValue(df.format(account.getDateModified()));
                writer.endNode();
            writer.startNode("shownotes");
                writer.setValue(Boolean.toString(account.getShowNotes()));
                writer.endNode();
            writer.startNode("notes");
                writer.setValue(StringEscapeUtils.escapeHtml(account.getNotes()));
                writer.endNode();
            writer.startNode("fields");
                LinkedHashMap<String, String> fieldMap = account.getTypeObject().getFieldMap();
                    for(String fieldName : fieldMap.keySet()) {
                        String fieldVal = fieldMap.get(fieldName);
                        if(!fieldVal.isEmpty()) {
                            writer.startNode("field");
                                writer.addAttribute("name", StringEscapeUtils.escapeHtml(fieldName));
                                writer.setValue(StringEscapeUtils.escapeHtml(fieldVal));
                            writer.endNode();
                        }
                    }
                writer.endNode();
        }
        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            // NOT USED
            Account account = new Account();
            return account;
        }
    }
    
    /**
    * Used by XStream to export types
    */
    class TypeConverter implements Converter {
        public boolean canConvert(Class classname) {
            return classname.equals(AccType.class);
        }

        /**
         * Exports the specified type to XML
         * @param value The Type object
         * @param writer Used by XStream
         * @param context Used by XStream
         */
         
        public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
            AccType type = (AccType)value;
            writer.startNode("title");
                writer.setValue(StringEscapeUtils.escapeHtml(type.getName()));
                writer.endNode();
            writer.startNode("shownotes");
                writer.setValue(Boolean.toString(type.getShowNotes()));
                writer.endNode();
            writer.startNode("id");
                writer.setValue(StringEscapeUtils.escapeHtml(type.getID()));
                writer.endNode();
            for(String fieldName : type.getFields()) {
                writer.startNode("field");
                    writer.setValue(StringEscapeUtils.escapeHtml(fieldName));
                    writer.endNode();
            }
        }
        public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
            // NOT USED
            Account account = new Account();
            return account;
        }
    }
}
